// SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QXmlStreamReader>

class Scanner
{
public:
    enum Option {
        ClientHeader,
        ClientCode,
    };

    explicit Scanner()
    {
    }
    ~Scanner()
    {
        delete m_xml;
    }

    bool parseArguments(int argc, char **argv);
    void printUsage();
    bool process(Option option);
    void printErrors();
    QString outputDir() const
    {
        return m_outputDir;
    }

private:
    struct WaylandEnumEntry
    {
        QByteArray name;
        QByteArray value;
        QByteArray summary;
    };

    struct WaylandEnum
    {
        QByteArray name;

        std::vector<WaylandEnumEntry> entries;
    };

    struct WaylandArgument
    {
        QByteArray name;
        QByteArray type;
        QByteArray interface;
        QByteArray summary;
        bool allowNull;
    };

    struct WaylandEvent
    {
        bool request;
        QByteArray name;
        QByteArray type;
        std::vector<WaylandArgument> arguments;
    };

    struct WaylandInterface
    {
        QByteArray name;
        int version;

        std::vector<WaylandEnum> enums;
        std::vector<WaylandEvent> events;
        std::vector<WaylandEvent> requests;
    };

    bool parseOption(const QByteArray &str);

    QByteArray byteArrayValue(const QXmlStreamReader &xml, const char *name);
    int intValue(const QXmlStreamReader &xml, const char *name, int defaultValue = 0);
    bool boolValue(const QXmlStreamReader &xml, const char *name);
    WaylandEvent readEvent(QXmlStreamReader &xml, bool request);
    Scanner::WaylandEnum readEnum(QXmlStreamReader &xml);
    Scanner::WaylandInterface readInterface(QXmlStreamReader &xml);
    QByteArray waylandToCType(const QByteArray &waylandType, const QByteArray &interface, bool cStyle = false);
    const Scanner::WaylandArgument *newIdArgument(const std::vector<WaylandArgument> &arguments);

    void printEvent(const WaylandEvent &e, bool omitNames = false, bool withResource = false);
    void printEventHandlerSignature(const WaylandEvent &e, const char *interfaceName);
    void printEnums(const std::vector<WaylandEnum> &enums);

    bool ignoreInterface(const QByteArray &name);

    QByteArray m_protocolName;
    QByteArray m_protocolFilePath;
    QByteArray m_outputDir;
    QByteArray m_scannerName;
    QVector<QByteArray> m_includes;
    QXmlStreamReader *m_xml = nullptr;
    FILE *exportFile;
};

bool Scanner::parseArguments(int argc, char **argv)
{
    QVector<QByteArray> args;
    args.reserve(argc);
    for (int i = 0; i < argc; ++i)
        args << QByteArray(argv[i]);

    m_scannerName = args[0];

    if (argc <= 1)
        return false;

    m_protocolFilePath = args[1];
    m_outputDir = QDir::currentPath().toUtf8();

    if (argc > 2 && !args[2].startsWith('-')) {
        // legacy positional arguments
        if (argc >= 3) {
            m_outputDir = args[2];
            if (!m_outputDir.startsWith("/"))
                m_outputDir = QDir::currentPath().toUtf8() + "/" + m_outputDir;
        }
        if (argc >= 4) {
            auto include = args[3];
            if (!include.isEmpty()) {
                auto includes = include.split(',');
                if (includes.size() > 1) {
                    for (const auto &i : includes)
                        m_includes << i.trimmed();
                } else {
                    m_includes << include.trimmed();
                }
            }
        }

    } else {
        // --output-dir=<path> (13 characters)
        // --add-include=<include> (14 characters)
        for (int pos = 2; pos < argc; pos++) {
            const QByteArray &option = args[pos];
            if (option.startsWith("--output-dir=")) {
                m_outputDir = option.mid(14);
            } else if (option.startsWith("--add-include=")) {
                auto include = option.mid(14);
                if (!include.isEmpty()) {
                    auto includes = include.split(',');
                    if (includes.size() > 1) {
                        for (const auto &i : includes)
                            m_includes << i.trimmed();
                    } else {
                        m_includes << include.trimmed();
                    }
                }
            } else {
                return false;
            }
        }
    }
    if (!m_outputDir.startsWith("/"))
        m_outputDir = "/" + m_outputDir;

    return true;
}

void Scanner::printUsage()
{
    fprintf(stderr, "Usage: %s protocol_xml_file [--output-dir=<path>] [--add-include=<include_1, include_2,...>]\n", m_scannerName.constData());
}

QByteArray Scanner::byteArrayValue(const QXmlStreamReader &xml, const char *name)
{
    if (xml.attributes().hasAttribute(name))
        return xml.attributes().value(name).toUtf8();
    return QByteArray();
}

int Scanner::intValue(const QXmlStreamReader &xml, const char *name, int defaultValue)
{
    bool ok;
    int result = byteArrayValue(xml, name).toInt(&ok);
    return ok ? result : defaultValue;
}

bool Scanner::boolValue(const QXmlStreamReader &xml, const char *name)
{
    return byteArrayValue(xml, name) == "true";
}

Scanner::WaylandEvent Scanner::readEvent(QXmlStreamReader &xml, bool request)
{
    WaylandEvent event = {
        .request = request,
        .name = byteArrayValue(xml, "name"),
        .type = byteArrayValue(xml, "type"),
        .arguments = {},
    };
    while (xml.readNextStartElement()) {
        if (xml.name() == QLatin1String("arg")) {
            WaylandArgument argument = {
                .name = byteArrayValue(xml, "name"),
                .type = byteArrayValue(xml, "type"),
                .interface = byteArrayValue(xml, "interface"),
                .summary = byteArrayValue(xml, "summary"),
                .allowNull = boolValue(xml, "allowNull"),
            };
            event.arguments.push_back(std::move(argument));
        }

        xml.skipCurrentElement();
    }
    return event;
}

Scanner::WaylandEnum Scanner::readEnum(QXmlStreamReader &xml)
{
    WaylandEnum result = {
        .name = byteArrayValue(xml, "name"),
        .entries = {},
    };

    while (xml.readNextStartElement()) {
        if (xml.name() == QLatin1String("entry")) {
            WaylandEnumEntry entry = {
                .name = byteArrayValue(xml, "name"),
                .value = byteArrayValue(xml, "value"),
                .summary = byteArrayValue(xml, "summary"),
            };
            result.entries.push_back(std::move(entry));
        }

        xml.skipCurrentElement();
    }

    return result;
}

Scanner::WaylandInterface Scanner::readInterface(QXmlStreamReader &xml)
{
    WaylandInterface interface = {
        .name = byteArrayValue(xml, "name"),
        .version = intValue(xml, "version", 1),
        .enums = {},
        .events = {},
        .requests = {},
    };

    while (xml.readNextStartElement()) {
        if (xml.name() == QLatin1String("event"))
            interface.events.push_back(readEvent(xml, false));
        else if (xml.name() == QLatin1String("request"))
            interface.requests.push_back(readEvent(xml, true));
        else if (xml.name() == QLatin1String("enum"))
            interface.enums.push_back(readEnum(xml));
        else
            xml.skipCurrentElement();
    }

    return interface;
}

QByteArray Scanner::waylandToCType(const QByteArray &waylandType, const QByteArray &interface, bool cStyle)
{
    if (waylandType == "string")
        return "const char *";
    else if (waylandType == "int")
        return "int32_t";
    else if (waylandType == "uint")
        return "uint32_t";
    else if (waylandType == "fixed")
        return "wl_fixed_t";
    else if (waylandType == "fd")
        return "int32_t";
    else if (waylandType == "array")
        return "wl_array *";
    else if (waylandType == "object" || waylandType == "new_id") {
        if (interface.isEmpty())
            return "struct ::wl_object *";
        if (cStyle)
            return "struct ::" + interface + " *";
        else
            return interface + "_t *";
    }
    return waylandType;
}

const Scanner::WaylandArgument *Scanner::newIdArgument(const std::vector<WaylandArgument> &arguments)
{
    for (const WaylandArgument &a : arguments) {
        if (a.type == "new_id")
            return &a;
    }
    return nullptr;
}

void Scanner::printEvent(const WaylandEvent &e, bool omitNames, bool withResource)
{
    fprintf(exportFile, "%s(", e.name.constData());
    bool needsComma = false;
    for (const WaylandArgument &a : e.arguments) {
        bool isNewId = a.type == "new_id";
        if (isNewId && (a.interface.isEmpty() != e.request))
            continue;
        if (needsComma)
            fprintf(exportFile, ", ");
        needsComma = true;
        if (isNewId && e.request) {
            fprintf(exportFile, "const struct ::wl_interface *%s, uint32_t%s", omitNames ? "" : "interface", omitNames ? "" : " version");
            continue;
        }

        QByteArray type = waylandToCType(a.type, a.interface, false);
        fprintf(exportFile, "%s%s%s", type.constData(), type.endsWith("&") || type.endsWith("*") ? "" : " ", a.name.constData());
    }
    fprintf(exportFile, ")");
}

void Scanner::printEventHandlerSignature(const WaylandEvent &e, const char *interfaceName)
{
    fprintf(exportFile, "handle_%s(void *data, ", e.name.constData());
    fprintf(exportFile, "struct ::%s *object", interfaceName);
    for (const WaylandArgument &a : e.arguments) {
        fprintf(exportFile, ", ");
        bool isNewId = a.type == "new_id";
        QByteArray cType = waylandToCType(a.type, a.interface, true);
        fprintf(exportFile, "%s%s%s", cType.constData(), cType.endsWith("*") ? "" : " ", a.name.constData());
    }
    fprintf(exportFile, ")");
}

void Scanner::printEnums(const std::vector<WaylandEnum> &enums)
{
    for (const WaylandEnum &e : enums) {
        fprintf(exportFile, "\n");
        fprintf(exportFile, "    enum %s {\n", e.name.constData());
        for (const WaylandEnumEntry &entry : e.entries) {
            fprintf(exportFile, "        %s_%s = %s,", e.name.constData(), entry.name.constData(), entry.value.constData());
            if (!entry.summary.isNull())
                fprintf(exportFile, " // %s", entry.summary.constData());
            fprintf(exportFile, "\n");
        }
        fprintf(exportFile, "    };\n");
    }
}

bool Scanner::ignoreInterface(const QByteArray &name)
{
    return name == "wl_display";
}

bool Scanner::process(Option option)
{
    QFile file(m_protocolFilePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        fprintf(stderr, "Unable to open file %s\n", m_protocolFilePath.constData());
        return false;
    }

    m_xml = new QXmlStreamReader(&file);
    if (!m_xml->readNextStartElement())
        return false;

    if (m_xml->name() != QLatin1String("protocol")) {
        m_xml->raiseError(QStringLiteral("The file is not a wayland protocol file."));
        return false;
    }

    m_protocolName = byteArrayValue(*m_xml, "name");

    if (m_protocolName.isEmpty()) {
        m_xml->raiseError(QStringLiteral("Missing protocol name."));
        return false;
    }

    QByteArray baseName = QFileInfo(m_protocolFilePath).baseName().replace('_', '-').toUtf8();

    if (option == ClientHeader) {
        QProcess process;
        process.start("wayland-scanner", QStringList() << "client-header" << m_protocolFilePath << m_outputDir + "/wayland-" + baseName + "-client-protocol.h");
        if (!process.waitForFinished()) {
            fprintf(stderr, "Unable to run wayland-scanner: %s\n", process.errorString().toUtf8().constData());
            return false;
        }

        exportFile = fopen(baseName + ".h", "w");
    } else {
        QProcess process;
        process.start("wayland-scanner", QStringList() << "private-code" << m_protocolFilePath << m_outputDir + "/wayland-" + baseName + "-protocol.c");
        if (!process.waitForFinished()) {
            fprintf(stderr, "Unable to run wayland-scanner: %s\n", process.errorString().toUtf8().constData());
            return false;
        }

        exportFile = fopen(baseName + ".cpp", "w");
    }

    std::vector<WaylandInterface> interfaces;

    while (m_xml->readNextStartElement()) {
        if (m_xml->name() == QLatin1String("interface"))
            interfaces.push_back(readInterface(*m_xml));
        else
            m_xml->skipCurrentElement();
    }

    if (m_xml->hasError())
        return false;

    fprintf(exportFile, "// This file was generated by ukuiwaylandscanner\n");
    fprintf(exportFile, "// source file is %s\n\n", qPrintable(m_protocolFilePath));

    if (option == ClientHeader) {
        fprintf(exportFile, "#pragma once\n");
        fprintf(exportFile, "\n");
        for (auto b : qAsConst(m_includes))
            fprintf(exportFile, "#include \"%s\"\n", b.constData());
        fprintf(exportFile, "\n");

        fprintf(exportFile, "#include \"wayland-%s-client-protocol.h\"\n", baseName.constData());
        fprintf(exportFile, "\n");

        fprintf(exportFile, "#include <ukui-wayland-client.h>\n\n");
        fprintf(exportFile, "#include <functional>\n");
        fprintf(exportFile, "\n");
        fprintf(exportFile, "\n");
        fprintf(exportFile, "struct wl_registry;\n");
        fprintf(exportFile, "\n");
        fprintf(exportFile, "\n");
        fprintf(exportFile, "namespace ukuiwaylandclient\n{\n");

        fprintf(exportFile, "\n");
        for (const WaylandInterface &interface : interfaces) {
            if (ignoreInterface(interface.name))
                continue;

            const char *interfaceName = (interface.name + "_t").constData();
            fprintf(exportFile, "class %s;\n", interfaceName);
        }
        fprintf(exportFile, "\n");

        bool needsNewLine = false;
        for (const WaylandInterface &interface : interfaces) {
            if (ignoreInterface(interface.name))
                continue;

            if (needsNewLine)
                fprintf(exportFile, "\n");
            needsNewLine = true;

            const char *interfaceName = interface.name.constData();

            fprintf(exportFile, "class %s_t\n{\n", interfaceName);
            fprintf(exportFile, "public:\n");
            fprintf(exportFile, "    %s_t(wl_registry_t *registry, int id, int version);\n", interfaceName);
            fprintf(exportFile, "    %s_t(struct ::wl_registry *registry, int id, int version);\n", interfaceName);
            fprintf(exportFile, "    %s_t(struct ::%s *object);\n", interfaceName, interfaceName);
            fprintf(exportFile, "    %s_t();\n", interfaceName);
            fprintf(exportFile, "\n");
            fprintf(exportFile, "    ~%s_t();\n", interfaceName);
            fprintf(exportFile, "\n");
            fprintf(exportFile, "    void init(struct ::wl_registry *registry, int id, int version);\n");
            fprintf(exportFile, "    void init(struct ::%s *object);\n", interfaceName);
            fprintf(exportFile, "\n");
            fprintf(exportFile, "    struct ::%s *object()\n    {\n        return m_%s;\n    }\n", interfaceName, interfaceName);
            fprintf(exportFile, "    const struct ::%s *object() const\n    {\n        return m_%s;\n    }\n", interfaceName, interfaceName);
            fprintf(exportFile, "    static %s_t *fromObject(struct ::%s *object);\n", interfaceName, interfaceName);
            fprintf(exportFile, "\n");
            fprintf(exportFile, "    bool isInitialized() const;\n");
            fprintf(exportFile, "\n");
            fprintf(exportFile, "    static const struct ::wl_interface *interface();\n");

            printEnums(interface.enums);

            if (!interface.requests.empty()) {
                fprintf(exportFile, "\n");
                for (const WaylandEvent &e : interface.requests) {
                    const WaylandArgument *new_id = newIdArgument(e.arguments);
                    QByteArray new_id_str = "void ";
                    if (new_id) {
                        if (new_id->interface.isEmpty())
                            new_id_str = "void *";
                        else
                            new_id_str = new_id->interface + "_t *";
                    }
                    fprintf(exportFile, "    %s", new_id_str.constData());
                    printEvent(e);
                    fprintf(exportFile, ";\n");
                }
            }

            bool hasEvents = !interface.events.empty();

            if (hasEvents) {
                fprintf(exportFile, "\n");
                for (const WaylandEvent &e : interface.events) {
                    fprintf(exportFile, "    std::function<void");
                    if (e.arguments.size() > 0) {
                        fprintf(exportFile, "(");
                        for (int i = 0; i < e.arguments.size(); i++) {
                            const WaylandArgument &a = e.arguments[i];
                            QByteArray cType = waylandToCType(a.type, a.interface, true);
                            if (i == e.arguments.size() - 1)
                                fprintf(exportFile, "%s)> &on_%s();\n", cType.constData(), e.name.constData());
                            else
                                fprintf(exportFile, "%s, ", cType.constData());
                        }
                    } else {
                        fprintf(exportFile, "()> &on_%s();\n", e.name.constData());
                    }
                }
            }
            fprintf(exportFile, "\n");

            fprintf(exportFile, "private:\n");
            if (hasEvents) {
                for (const WaylandEvent &e : interface.events) {
                    fprintf(exportFile, "    std::function<void");
                    if (e.arguments.size() > 0) {
                        fprintf(exportFile, "(");
                        for (int i = 0; i < e.arguments.size(); i++) {
                            const WaylandArgument &a = e.arguments[i];
                            QByteArray cType = waylandToCType(a.type, a.interface, true);
                            if (i == e.arguments.size() - 1)
                                fprintf(exportFile, "%s)> ", cType.constData());
                            else
                                fprintf(exportFile, "%s, ", cType.constData());
                        }
                    } else {
                        fprintf(exportFile, "()> ");
                    }
                    fprintf(exportFile, "%s;\n", e.name.constData());
                }
                fprintf(exportFile, "\n");

                fprintf(exportFile, "    void init_listener();\n");
                fprintf(exportFile, "    static const struct %s_listener m_%s_listener;\n", interfaceName, interfaceName);
                for (const WaylandEvent &e : interface.events) {
                    fprintf(exportFile, "    static void ");

                    printEventHandlerSignature(e, interfaceName);
                    fprintf(exportFile, ";\n");
                }
            }
            fprintf(exportFile, "    struct ::%s *m_%s;\n", interfaceName, interfaceName);
            fprintf(exportFile, "};\n");
        }
        fprintf(exportFile, "}\n");
        fprintf(exportFile, "\n");
    }

    if (option == ClientCode) {
        fprintf(exportFile, "#include \"%s.h\"\n", baseName.constData());
        fprintf(exportFile, "\n");
        fprintf(exportFile, "\n");
        fprintf(exportFile, "namespace ukuiwaylandclient\n{\n");
        fprintf(exportFile, "\n");

        // wl_registry_bind is part of the protocol, so we can't use that... instead we use core
        // libwayland API to do the same thing a wayland-scanner generated wl_registry_bind would.
        fprintf(exportFile, "static inline void *wlRegistryBind(struct ::wl_registry *registry, uint32_t name, const struct ::wl_interface *interface, uint32_t version)\n");
        fprintf(exportFile, "{\n");
        fprintf(exportFile, "    const uint32_t bindOpCode = 0;\n");
        fprintf(exportFile, "#if (WAYLAND_VERSION_MAJOR == 1 && WAYLAND_VERSION_MINOR > 10) || WAYLAND_VERSION_MAJOR > 1\n");
        fprintf(exportFile, "    return (void *) wl_proxy_marshal_constructor_versioned((struct wl_proxy *) registry,\n");
        fprintf(exportFile, "        bindOpCode, interface, version, name, interface->name, version, nullptr);\n");
        fprintf(exportFile, "#else\n");
        fprintf(exportFile, "    return (void *) wl_proxy_marshal_constructor((struct wl_proxy *) registry,\n");
        fprintf(exportFile, "        bindOpCode, interface, name, interface->name, version, nullptr);\n");
        fprintf(exportFile, "#endif\n");
        fprintf(exportFile, "}\n");
        fprintf(exportFile, "\n");

        bool needsNewLine = false;
        for (const WaylandInterface &interface : interfaces) {

            if (ignoreInterface(interface.name))
                continue;

            if (needsNewLine)
                fprintf(exportFile, "\n");
            needsNewLine = true;

            const char *interfaceName = interface.name.constData();

            bool hasEvents = !interface.events.empty();

            bool hasDestroyRequest = false;
            for (const WaylandEvent &e : interface.requests) {
                if (e.name == "destroy") {
                    hasDestroyRequest = true;
                    break;
                }
            }

            fprintf(exportFile, "%s_t::%s_t(wl_registry_t *registry, int id, int version)\n", interfaceName, interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    init(registry->object(), id, version);\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "%s_t::%s_t(struct ::wl_registry *registry, int id, int version)\n", interfaceName, interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    init(registry, id, version);\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "%s_t::%s_t(struct ::%s *obj)\n", interfaceName, interfaceName, interfaceName);
            fprintf(exportFile, "    : m_%s(obj)\n", interfaceName);
            fprintf(exportFile, "{\n");
            if (hasEvents)
                fprintf(exportFile, "    init_listener();\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "%s_t::%s_t()\n", interfaceName, interfaceName);
            fprintf(exportFile, "    : m_%s(nullptr)\n", interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "%s_t::~%s_t()\n", interfaceName, interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    if (m_%s) {\n", interfaceName);
            if (hasEvents)
                fprintf(exportFile, "        wl_proxy_set_user_data((struct ::wl_proxy *) m_%s, nullptr);\n", interfaceName);
            if (hasDestroyRequest)
                fprintf(exportFile, "        %s_destroy(m_%s);\n", interfaceName, interfaceName);
            else
                fprintf(exportFile, "        wl_proxy_destroy((struct ::wl_proxy *) m_%s);\n", interfaceName);
            fprintf(exportFile, "        m_%s = nullptr;\n", interfaceName);
            fprintf(exportFile, "    }\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "void %s_t::init(struct ::wl_registry *registry, int id, int version)\n", interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    m_%s = static_cast<struct ::%s *>(wlRegistryBind(registry, id, &%s_interface, version));\n", interfaceName, interfaceName, interfaceName);
            if (hasEvents)
                fprintf(exportFile, "    init_listener();\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "void %s_t::init(struct ::%s *obj)\n", interfaceName, interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    m_%s = obj;\n", interfaceName);
            if (hasEvents)
                fprintf(exportFile, "    init_listener();\n");
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "%s_t *%s_t::fromObject(struct ::%s *object)\n", interfaceName, interfaceName, interfaceName);
            fprintf(exportFile, "{\n");
            if (hasEvents) {
                fprintf(exportFile, "    if (wl_proxy_get_listener((struct ::wl_proxy *)object) != (void *)&m_%s_listener)\n", interfaceName);
                fprintf(exportFile, "        return nullptr;\n");
            }
            fprintf(exportFile, "    return static_cast<%s_t *>(%s_get_user_data(object));\n", interfaceName, interfaceName);
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "bool %s_t::isInitialized() const\n", interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    return m_%s != nullptr;\n", interfaceName);
            fprintf(exportFile, "}\n");
            fprintf(exportFile, "\n");

            fprintf(exportFile, "const struct wl_interface *%s_t::interface()\n", interfaceName);
            fprintf(exportFile, "{\n");
            fprintf(exportFile, "    return &::%s_interface;\n", interfaceName);
            fprintf(exportFile, "}\n");

            for (const WaylandEvent &e : interface.requests) {
                fprintf(exportFile, "\n");
                const WaylandArgument *new_id = newIdArgument(e.arguments);
                QByteArray new_id_str = "void ";
                if (new_id) {
                    if (new_id->interface.isEmpty())
                        new_id_str = "void *";
                    else
                        new_id_str = new_id->interface + "_t *";
                }
                fprintf(exportFile, "%s%s_t::", new_id_str.constData(), interfaceName);
                printEvent(e);
                fprintf(exportFile, "\n");
                fprintf(exportFile, "{\n");
                for (const WaylandArgument &a : e.arguments) {
                    if (a.type != "array")
                        continue;
                    QByteArray array = a.name + "_data";
                    const char *arrayName = array.constData();
                    const char *variableName = a.name.constData();
                    fprintf(exportFile, "    struct wl_array %s;\n", arrayName);
                    fprintf(exportFile, "    %s.size = %s.size();\n", arrayName, variableName);
                    fprintf(exportFile, "    %s.data = static_cast<void *>(const_cast<char *>(%s.constData()));\n", arrayName, variableName);
                    fprintf(exportFile, "    %s.alloc = 0;\n", arrayName);
                    fprintf(exportFile, "\n");
                }
                int actualArgumentCount = new_id ? int(e.arguments.size()) - 1 : int(e.arguments.size());
                fprintf(exportFile, "    %s%s%s_%s(", new_id ? (new_id_str == "void *" ? "return " : "return new ") : "", new_id ? (new_id_str == "void *" ? "" : (new_id->interface + "_t(").constData()) : "", interfaceName, e.name.constData());
                fprintf(exportFile, "m_%s%s", interfaceName, actualArgumentCount > 0 ? ", " : "");
                bool needsComma = false;
                for (const WaylandArgument &a : e.arguments) {
                    bool isNewId = a.type == "new_id";
                    if (isNewId && !a.interface.isEmpty())
                        continue;
                    if (needsComma)
                        fprintf(exportFile, ", ");
                    needsComma = true;
                    if (isNewId) {
                        fprintf(exportFile, "interface, version");
                    } else {
                        QByteArray qtType = waylandToCType(a.type, a.interface, e.request);
                        if (a.type == "string")
                            fprintf(exportFile, "%s", a.name.constData());
                        else if (a.type == "array")
                            fprintf(exportFile, "&%s_data", a.name.constData());
                        else if (a.type == "object" || a.type == "new_id") {
                            if (!a.interface.isEmpty())
                                fprintf(exportFile, "%s->object()", a.name.constData());
                            else
                                fprintf(exportFile, "%s", a.name.constData());

                        } else
                            fprintf(exportFile, "%s", a.name.constData());
                    }
                }
                fprintf(exportFile, "%s;\n", (new_id && new_id_str != "void *") ? "))" : ")");
                if (e.type == "destructor")
                    fprintf(exportFile, "    m_%s = nullptr;\n", interfaceName);
                fprintf(exportFile, "}\n");
            }

            if (hasEvents) {
                fprintf(exportFile, "\n");
                for (const WaylandEvent &e : interface.events) {
                    fprintf(exportFile, "std::function<void");
                    if (e.arguments.size() > 0) {
                        fprintf(exportFile, "(");
                        for (int i = 0; i < e.arguments.size(); i++) {
                            const WaylandArgument &a = e.arguments[i];
                            QByteArray cType = waylandToCType(a.type, a.interface, true);
                            if (i == e.arguments.size() - 1)
                                fprintf(exportFile, "%s)> &%s_t::on_%s()\n{\n", cType.constData(), interfaceName, e.name.constData());
                            else
                                fprintf(exportFile, "%s, ", cType.constData());
                        }
                    } else {
                        fprintf(exportFile, "()> &%s_t::on_%s()\n{\n", interfaceName, e.name.constData());
                    }
                    fprintf(exportFile, "    return %s;\n", e.name.constData());
                    fprintf(exportFile, "}\n");

                    // fprintf(exportFile, "    if (%s)\n", e.name.constData());
                    // fprintf(exportFile, "        %s(", e.name.constData());

                    // for (int i = 0; i < e.arguments.size(); i++) {
                    //     const WaylandArgument &a = e.arguments[i];
                    //     if (i == e.arguments.size() - 1)
                    //         fprintf(exportFile, "%s", a.name.constData());
                    //     else
                    //         fprintf(exportFile, "%s, ", a.name.constData());
                    // }
                    // fprintf(exportFile, ");\n");
                    fprintf(exportFile, "\n");
                    fprintf(exportFile, "void %s_t::", interfaceName);
                    printEventHandlerSignature(e, interfaceName);
                    fprintf(exportFile, "\n");
                    fprintf(exportFile, "{\n");
                    fprintf(exportFile, "    %s_t *t = static_cast<%s_t *>(data);\n", interfaceName, interfaceName);
                    fprintf(exportFile, "    if(t->%s)\n", e.name.constData());
                    fprintf(exportFile, "        t->%s(", e.name.constData());

                    bool needsComma = false;
                    for (const WaylandArgument &a : e.arguments) {
                        if (needsComma)
                            fprintf(exportFile, ", ");
                        needsComma = true;
                        fprintf(exportFile, "%s", a.name.constData());
                    }
                    fprintf(exportFile, ");\n");

                    fprintf(exportFile, "}\n");
                    fprintf(exportFile, "\n");
                }
                fprintf(exportFile, "const struct %s_listener %s_t::m_%s_listener = {\n", interfaceName, interfaceName, interfaceName);
                for (const WaylandEvent &e : interface.events) {
                    fprintf(exportFile, "    %s_t::handle_%s,\n", interfaceName, e.name.constData());
                }
                fprintf(exportFile, "};\n");
                fprintf(exportFile, "\n");

                fprintf(exportFile, "void %s_t::init_listener()\n", interfaceName);
                fprintf(exportFile, "{\n");
                fprintf(exportFile, "    %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName);
                fprintf(exportFile, "}\n");
            }
        }
        fprintf(exportFile, "}\n");
        fprintf(exportFile, "\n");
    }
    fclose(exportFile);
    exportFile = nullptr;

    return true;
}

void Scanner::printErrors()
{
    if (m_xml->hasError())
        fprintf(stderr, "XML error: %s\nLine %lld, column %lld\n", m_xml->errorString().toLocal8Bit().constData(), m_xml->lineNumber(), m_xml->columnNumber());
}

int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);

    if (argc == 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) {
        fprintf(stdout, "Usage: ukuiwaylandscanner protocol_xml_file [--output-dir=<path>] [--add-include=<include_1, include_2,...>]\n");
        return EXIT_SUCCESS;
    }

    Scanner scanner;

    if (!scanner.parseArguments(argc, argv)) {
        scanner.printUsage();
        return EXIT_FAILURE;
    }

    if (!scanner.process(Scanner::ClientHeader)) {
        scanner.printErrors();
        return EXIT_FAILURE;
    }

    if (!scanner.process(Scanner::ClientCode)) {
        scanner.printErrors();
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
